/*
 * CRPanel.java
 *
 * Created on Aug 25, 2010, 1:26:56 AM
 */
package chainreaction;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 *
 * @author Jordan
 */
public class CRPanel extends javax.swing.JPanel implements Runnable, MouseListener {

    static final int MAX_BOMBS = 30;
    static final int STAT_DISP_SIZE = 50;
    static final int POINT_VALUE = 30;
    static final int GOLD_VALUE = 50;
    static final int TIMEOUT = 10000; // 10 seconds
    
    private final Object bombLock = new Object();
    
    private ArrayList<Bomb> bombs;
    private Rectangle container;
    private long gameTimer, currTime, delay, timeout;
    private int timeFlashCounter;

    // Statistics...
    private Map<Long, Integer> explosionMap;
    private int score;
    private int timeRemaining;
    private int highestScore;
    private int totalTime;
    
    /** Creates new form CRPanel */
    public CRPanel(int width, int height) {
        container = new Rectangle(width, height);
        resetScores();
        addMouseListener(this);
        new Thread(this).start();
    }
    private void resetScores() {
        score = 0;
        highestScore = 0;
        timeRemaining = 25;
        bombs = new ArrayList<Bomb>();
        explosionMap = new HashMap<Long, Integer>();
        timeFlashCounter = 0;
        totalTime = 0;

        System.gc();
    }
    public void run() {
        gameTimer = System.currentTimeMillis();
        timeout = System.currentTimeMillis() + TIMEOUT;
        while (timeRemaining > 0) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException ex) {
                Logger.getLogger(CRPanel.class.getName()).log(Level.SEVERE, null, ex);
            }

            // Count down timer...
            currTime = System.currentTimeMillis();
            if ((currTime - gameTimer) / 1000 > 0) {
                timeRemaining--;
                totalTime++;
                gameTimer = currTime;
            }

            // Add bombs if necessary...
            if (bombs.size() < MAX_BOMBS * (1 + (totalTime / 50))) {
                if (currTime >= delay) {
                  delay = currTime + (int)(Math.random() * 500.0);
                  addBomb();
                }
            } else {
                if (currTime >= delay) {
                  delay = currTime + (int)(Math.random() * 2000.0);
                  addBomb();
                }
            }

            // Start removing "special" bombs if the timeout is too high
            if (currTime >= timeout) {
                if (bombs.size() > 0) {
                    for (int ii = 0;ii < bombs.size();ii++) {
                        if (bombs.get(ii).getType() != Bomb.Type.Standard) {
                            bombs.remove(ii);
                            break;
                        }
                    }
                    timeout += 5000;
                }
            }

            // Check collisions... (n^2 unfortunately...)
            synchronized (bombLock) {
                Point loc, dir;
                int rad;
                for (int i = 0; i < bombs.size(); i++) {
                    Bomb b = bombs.get(i);
                    loc = b.getLocation();
                    dir = b.getDirection();
                    rad = b.getRadius();


                    // Ignore collisions on the sides if the bomb is exploding
                    if (!b.exploding()) {
                        if (loc.x + rad >= container.width || loc.x - rad <= 0) {
                            b.setDirection(new Point(-1 * dir.x, dir.y));
                        }
                        if (loc.y + rad >= container.height || loc.y - rad <= 0) {
                            b.setDirection(new Point(dir.x, -1 * dir.y));
                        }
                    } else {
                        for (int x = 0; x < bombs.size(); x++) {
                            Bomb b2 = bombs.get(x);
                            if (b2 == b || b2.exploding())
                                continue;
                            Point loc2 = b2.getLocation();
                            int rad2 = b2.getRadius();
                            int dist = (int)(Math.sqrt(Math.pow(loc.x - loc2.x, 2) +
                                    Math.pow(loc.y - loc2.y, 2)));
                            if (dist <= rad + rad2) {
                                // Collision
                                int newCount = explosionMap.get(b.getExplosionID()) + 1;
                                explosionMap.put(b.getExplosionID(), newCount);

                                if (newCount > highestScore)
                                    highestScore = newCount;

                                // Cherry bombs have bigger explosions
                                if (b2.getType() == Bomb.Type.Cherry)
                                    b2.setMaxRadius((int)((newCount * 5.0) / Math.exp(0.001*totalTime)) + b2.getMaxRadius());
                                if (b2.getType() == Bomb.Type.Money)
                                    score += newCount * GOLD_VALUE;
                                if (b2.getType() == Bomb.Type.Time) {
                                    int timeMult = newCount;
                                    if (newCount > 100)
                                        timeMult = 100;
                                    timeRemaining += (int)(timeMult/(3.0 * Math.exp(0.01*totalTime))) + 1;
                                    if (timeRemaining > 60)
                                        timeRemaining = 60;
                                } 

                                b2.explode(b.getExplosionID());
                                score += POINT_VALUE;

                                // Timeout resets on each new explosion
                                timeout = System.currentTimeMillis() + TIMEOUT;
                            }
                        }
                    }
                }
            }

            // Update bombs (synchronized to prevent bombs changing mid-iteration)
            synchronized (bombLock) {
                boolean hasTime = false;
                for (int i = 0; i < bombs.size(); i++) {
                    bombs.get(i).tick();

                    // Remove exploded bombs
                    if (bombs.get(i).getType() == Bomb.Type.Time)
                        hasTime = true;
                    if (bombs.get(i).exploded())
                        bombs.remove(i);
                }
                if (!hasTime)
                    bombs.add(new Bomb(container.width, container.height, Bomb.Type.Time));
            }
            // Repaint
            repaint();
        }
        int res = JOptionPane.showConfirmDialog(this, "Play again?", "Replay",
                JOptionPane.YES_NO_OPTION);

        if (res == JOptionPane.YES_OPTION) {
            resetScores();
            new Thread(this).start();
        } else {
            System.exit(0); // Normal termination
        }
    }

    public void mouseClicked(MouseEvent e) {

    }

    @Override
    public void paintComponent(Graphics g) {
        g.setColor(Color.black);
        g.fillRect(0, 0, container.width, container.height);
        g.setColor(Color.white);
        // Draw a window
        g.drawRect(0, 0, container.width - 1, container.height - 1);
        // Draw all the bombs
        Point loc;
        int rad;
        for (int i = 0; i < bombs.size(); i++) {
            Bomb b = bombs.get(i);
            loc = b.getLocation();
            rad = b.getRadius();
            g.setColor(b.getType().getColor());
            midpointCircle(g, loc.x, loc.y, rad, b.getType().getColor());
        }
        // Draw some stats...
        g.setColor(Color.BLACK);
        g.fillRect(0, this.getHeight() - STAT_DISP_SIZE, this.getWidth(), STAT_DISP_SIZE);
        g.setColor(Color.WHITE);
        FontMetrics fm = g.getFontMetrics();
        Font newFont = new Font(fm.getFont().getName(), fm.getFont().getStyle(), fm.getFont().getSize());
        if (this.timeRemaining < 3) {
            if (timeFlashCounter < 3)
                newFont = new Font(newFont.getName(), newFont.getStyle(), newFont.getSize() + 1);
            else
                newFont = new Font(newFont.getName(), newFont.getStyle(), newFont.getSize() - 1);
            if (timeFlashCounter >= 6)
                timeFlashCounter = 0;
            timeFlashCounter++;
        } else if (this.timeRemaining < 7) {
            if (timeFlashCounter < 6)
                newFont = new Font(newFont.getName(), newFont.getStyle(), newFont.getSize() + 1);
            else
                newFont = new Font(newFont.getName(), newFont.getStyle(), newFont.getSize() - 1);
            if (timeFlashCounter >= 12)
                timeFlashCounter = 0;
            timeFlashCounter++;
        }
        g.setFont(newFont);
        g.drawString("Time: " + this.timeRemaining, 5, this.getHeight() - fm.getFont().getSize());
        g.setFont(fm.getFont());
        String scoreString = "Score: " + this.score;
        g.drawString(
                scoreString,
                this.getWidth() - fm.stringWidth(scoreString),
                this.getHeight() - fm.getFont().getSize()
                );
        String explosionsString = "Largest reaction: " + this.highestScore + " bombs";
        g.drawString(
                explosionsString,
                (this.getWidth() - fm.stringWidth(explosionsString))/2,
                this.getHeight() - fm.getFont().getSize()
                );
    }

    private void midpointCircle(Graphics g, int x0, int y0, int radius, Color fillColor) {
        int func = 1 - radius;
        int dfdx = 1;
        int dfdy = -2 * radius;
        int x = 0;
        int y = radius;

        Color oldCol = g.getColor();
        // Draw the four "corners" of a circle
        // Fill inner circle...
        g.setColor(fillColor);
        g.drawLine(x0, y0 + radius, x0, y0 - radius);
        g.drawLine(x0 + radius, y0, x0 - radius, y0);

        // Draw a border
        g.setColor(Color.WHITE);
        g.drawLine(x0, y0 + radius, x0, y0 + radius); // draws a single pixel
        g.drawLine(x0, y0 - radius, x0, y0 - radius);
        g.drawLine(x0 + radius, y0, x0 + radius, y0);
        g.drawLine(x0 - radius, y0, x0 - radius, y0);

        // Iterate over the first quadrant
        while (x < y) {
            if (func >= 0) {
                y--;
                dfdy += 2;
                func += dfdy;
            }
            x++;
            dfdx += 2;
            func += dfdx;

            // Draw the next point in all four quadrants
            // Draw fill
            g.setColor(fillColor);
            g.drawLine(x0 + x, y0 + y, x0 - x, y0 + y);
            g.drawLine(x0 + x, y0 - y, x0 - x, y0 - y);
            g.drawLine(x0 + y, y0 + x, x0 - y, y0 + x);
            g.drawLine(x0 + y, y0 - x, x0 - y, y0 - x);

            // Draw border
            g.setColor(Color.WHITE);
            g.drawLine(x0 + x, y0 + y, x0 + x, y0 + y);
            g.drawLine(x0 - x, y0 + y, x0 - x, y0 + y);
            g.drawLine(x0 + x, y0 - y, x0 + x, y0 - y);
            g.drawLine(x0 - x, y0 - y, x0 - x, y0 - y);
            g.drawLine(x0 + y, y0 + x, x0 + y, y0 + x);
            g.drawLine(x0 - y, y0 + x, x0 - y, y0 + x);
            g.drawLine(x0 + y, y0 - x, x0 + y, y0 - x);
            g.drawLine(x0 - y, y0 - x, x0 - y, y0 - x);
        }
        g.setColor(oldCol);
    }

    private void addBomb() {
        double rand = Math.random();
        Bomb.Type type = Bomb.Type.Standard;
        if (rand < 0.03)
            type = Bomb.Type.Cherry;
        else if (rand < 0.08)
            type = Bomb.Type.Time;
        else if (rand < 0.12)
            type = Bomb.Type.Money;
        bombs.add(new Bomb(container.width, container.height, type));
    }

    public void mousePressed(MouseEvent e) {
        // Create a new trigger bomb
        long ID = System.currentTimeMillis();
        explosionMap.put(ID, 1);
        bombs.add(new Bomb(e.getX(), e.getY(), 1, ID));
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
}
